﻿using ElasticSearchTest.Model;
using Nest;
using PMS.Search.Domain.Common;
using PMS.Search.Domain.Entities;
using PMS.Search.Elasticsearch;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ElasticSearchTest
{
    public class ArticleEsService
    {
        private readonly EsClient _client;
        private readonly EsIndexConfig.Index _index = EsIndexConfig.Instance.ArticleIndex;

        public ArticleEsService(EsClient client)
        {
            _client = client;
        }
        public void Test()
        {
            var resp = CreateIndex();

            Guid? userId = null;
            int provinceId = 440000;
            int cityId = 440100;
            List<int> articleType = new List<int>()
            {

            };
            List<string> schTypes = new List<string>()
            {
                //"外国人高中"
            };
            List<int> areaId = new List<int>()
            {

            };
            
            var search = SearchAsync("", userId, provinceId, cityId, areaId, articleType, schTypes, null,0, 1, 10).GetAwaiter().GetResult();
            var total = SearchTotalAsync("", userId, provinceId, cityId, areaId, articleType, schTypes, null).GetAwaiter().GetResult();
        }

        private BoolQueryDescriptor<SearchArticle> CitySearch(BoolQueryDescriptor<SearchArticle> nqb, int? provinceId, int? cityId, List<int> areaId)
        {
            var boolQuery = nqb.Should(
                //全市
                s => s.Term("areas.cityId", cityId),
                //全省
                s => s.Bool(b => b.Must(
                      m => m.Term("areas.cityId", 0),
                      m => m.Term("areas.provinceId", provinceId)
                    )),
                //全国
                s => s.Bool(b => b.Must(
                      m => m.Term("areas.provinceId", 0)
                    ))
            );
            if (areaId.Any())
            {
                boolQuery.Must(m => m.Terms(t => t.Field("areas.areaId").Terms(areaId)));
            }
            return boolQuery;
        }

        public async Task<long> SearchTotalAsync(string keyword, Guid? userId, int? provinceId, int? cityId
            , List<int> areaId, List<int> articleTypes, List<string> schTypes, bool? isTop)
        {
            var queryDesc = GetQueryDescriptor(keyword, userId, provinceId, cityId, areaId, articleTypes, schTypes, isTop);
            var result = await _client.GetClient().CountAsync<SearchArticle>(c =>
                                    c.Index(_index.SearchIndex).Query(queryDesc)
                                );
            return result.Count;
        }

        public async Task<PaginationModel<SearchArticle>> SearchAsync(string keyword, Guid? userId, int? provinceId, int? cityId
            , List<int> areaIds, List<int> articleTypes, List<string> schTypes, bool? isTop
            , ArticleOrderBy orderBy, int pageIndex = 1, int pageSize = 10)
        {
            int from = (pageIndex - 1) * pageSize;
            int size = pageSize;

            Func<SortDescriptor<SearchArticle>, IPromise<IList<ISort>>> sort;
            switch (orderBy)
            {
                case ArticleOrderBy.HotDesc:
                    sort = s => s.Descending(a => a.ViewCount);
                    break;
                case ArticleOrderBy.HotAsc:
                    sort = s => s.Ascending(a => a.ViewCount);
                    break;
                case ArticleOrderBy.TimeDesc:
                    sort = s => s.Descending(a => a.Time);
                    break;
                case ArticleOrderBy.TimeAsc:
                    sort = s => s.Ascending(a => a.Time);
                    break;
                case ArticleOrderBy.TopTimeDesc:
                    sort = s => s.Descending(a => a.TopTime);
                    break;
                case ArticleOrderBy.Default:
                default:
                    if (!string.IsNullOrWhiteSpace(keyword))
                    {
                        sort = s => s.Descending(SortSpecialField.Score);
                    }
                    //无搜索词, 时间逆序
                    else
                    {
                        sort = s => s.Descending(a => a.Time);
                    }
                    break;
            }

            //日期衰减
            var queryDesc = GetQueryDescriptor(keyword, userId, provinceId, cityId, areaIds, articleTypes, schTypes, isTop);
            var searchDesc = new SearchDescriptor<SearchArticle>()
                .Index(_index.SearchIndex)
                .From(from)
                .Size(size)
                .Sort(sort)
                .Query(q =>
                    q.FunctionScore(fs =>
                        fs.Query(queryDesc)
                        .BoostMode(FunctionBoostMode.Sum)
                        .Functions(fun =>
                            fun.GaussDate(gd =>
                                gd.Field(f => f.Time)
                                .Origin(DateTime.Now)
                                .Scale(TimeSpan.FromDays(1))
                                .Offset(TimeSpan.FromDays(30)
                            )))
                   ));

            var result = await _client.GetClient().SearchAsync<SearchArticle>(searchDesc);
            var total = result.Total;
            var data = result.Hits.Select(q => q.Source).ToList();
            return PaginationModel.Build(data, total);
        }


        private Func<QueryContainerDescriptor<SearchArticle>, QueryContainer> GetQueryDescriptor(string keyword, Guid? userId, int? provinceId, int? cityId
            , List<int> areaIds, List<int> articleTypes, List<string> schTypes, bool? isTop)
        {
            var mustQuerys = new List<Func<QueryContainerDescriptor<SearchArticle>, QueryContainer>>
            {
                m => m.Term(t => t.Field(tf => tf.IsDeleted).Value(false))
            };
            var shouldQuerys = new List<Func<QueryContainerDescriptor<SearchArticle>, QueryContainer>>
            {
            };

            //关键字查询
            if (!string.IsNullOrEmpty(keyword))
            {
                var query = new MultiMatchQuery()
                {
                    //Fields = Infer.Field<SearchArticle>(f => f.Title).And("tags^10"),
                    Fields = new string[] { "title.pinyin", "tags^10" },
                    Query = keyword,
                    Analyzer = "ik_search_analyzer",
                };
                mustQuerys.Add(q => query);
                shouldQuerys.Add(s => s.Match(m => m.Field("title").Query(keyword).Boost(2)));
                shouldQuerys.Add(s => s.Term(m => m.Field("title.pinyin").Value(keyword).Boost(2)));
            }

            if (isTop != null)
            {
                mustQuerys.Add(m => m.Term(t => t.Field(tf => tf.ToTop).Value(isTop.Value)));
            }

            //达人作者查询
            if (userId != null)
            {
                var query = new TermQuery
                {
                    Field = Infer.Field<SearchArticle>(p => p.UserId),
                    Value = userId
                };
                mustQuerys.Add(mq => query);
            }

            //文章类型查询
            if (articleTypes?.Any() == true)
            {
                mustQuerys.Add(mq => mq.Terms(t => t.Field(tf => tf.ArticleType).Terms(articleTypes)));
            }

            //学校类型给查询
            if (schTypes?.Any() == true)
            {
                mustQuerys.Add(mq => mq.Terms(t => t.Field(tf => tf.SchTypes).Terms(schTypes)));
            }

            //城市和城区查询
            if (cityId > 0)
            {
                if (!(provinceId > 0))
                {
                    throw new ArgumentNullException("请传入城市所在省份");
                }

                //不限制城市, 但同城,同省,全国优先
                shouldQuerys.Add(mq => mq.Nested(n =>
                    n.Path(p => p.Areas)
                    .Query(nq =>
                        nq.Bool(nqb => nqb.Should(
                                //全市
                                s => s.Term("areas.cityId", cityId, 3),
                                //全省
                                s => s.Bool(b => b.Must(
                                      m => m.Term("areas.cityId", 0),
                                      m => m.Term("areas.provinceId", provinceId, 2)
                                    )),
                                //全国
                                s => s.Bool(b => b.Must(
                                      m => m.Term("areas.provinceId", 0, 1)
                                    ))
                            )
                    ))
                ));

                if (areaIds.Any())
                {
                    mustQuerys.Add(mq => mq.Nested(n =>
                        n.Path(p => p.Areas)
                        .Query(nq =>
                            nq.Bool(nqb => nqb.Must(m => m.Terms(t => t.Field("areas.areaId").Terms(areaIds)))
                        ))
                    ));
                }
            }
            return q => q.Bool(b => b.Must(mustQuerys).Should(shouldQuerys));
        }

        public CreateIndexResponse CreateIndex()
        {
            var resp = _client.GetClient().Indices.Delete(_index.Name);
            var createIndexResponse = _client.GetClient().Indices.Create(_index.Name, c => c
               .Aliases(ad => ad.Alias(_index.Alias))
               .Settings(s => s
                   .Setting("max_ngram_diff", 20)
                   .Setting("max_result_window", 500000)
                   .Analysis(AnalyzeBuilder.GetAnalysis())
               )
               .Map<SearchArticle>(mm => mm
               .Dynamic(false)
                  .Properties(p => p
                      .Keyword(t => t
                          .Name(n => n.Id)
                      )
                      .Keyword(t => t
                           .Name(n => n.UserId)
                       )
                      .Text(t => t
                          .Name(n => n.Title)
                          .Fields(f => f
                                .Text(ft
                                    => ft.Name("cntext")
                                    .Analyzer(AnalyzeBuilder.IkNgramAnalyzer)
                                    .SearchAnalyzer(AnalyzeBuilder.IkSearchAnalyzer))
                                .Text(ft
                                    => ft.Name("pinyin")
                                    .Analyzer(AnalyzeBuilder.IkPinyinAnalyzer)
                                    .SearchAnalyzer(AnalyzeBuilder.IkPinyinSearchAnalyzer))
                          )
                      )
                      .Text(t => t.Name(n => n.Tags).Fields(ff => ff.Text(fk => fk.Name("keyword").Analyzer("string_lowercase"))))
                      .Date(t => t.Name(n => n.Time))
                      .Date(t => t.Name(n => n.UpdateTime))
                      .Date(t => t.Name(n => n.TopTime))
                      .Boolean(t => t.Name(n => n.ToTop))
                      .Boolean(t => t.Name(n => n.IsDeleted))
                      .Number(t => t.Name(n => n.ArticleType))
                      .Number(t => t.Name(n => n.ViewCount))
                      .Keyword(t => t.Name(n => n.SchTypes)) //.Fields(ff => ff.Keyword(fk => fk.Name("keyword")))
                      .Nested<SearchArticle.ArticleArea>(t => t.Name(n => n.Areas).AutoMap())
                      .Keyword(t => t.Name(n => n.CityCodes))
                      .Number(t => t.Name(n => n.Pv))
                      .Number(t => t.Name(n => n.Uv))
                  )
              )
            );
            return createIndexResponse;
        }
    }
}
